﻿using System;
using System.Collections.Generic;

#if WINDOWS_PHONE
namespace Fibre.WP7.Threading
#else
#if SILVERLIGHT
namespace Fibre.Silverlight.Threading
#else
namespace Fibre.Threading
#endif
#endif
{
    /// <summary>
    /// Provides a fluent API for the simultanious worker.
    /// </summary>
    public class AsyncWork : ICanWait, IAddWork
    {
        private readonly Queue<IWorkContextBase> _work = new Queue<IWorkContextBase>();
        private Action _completeCallback;
        private readonly SimultaneousWorker _worker = new SimultaneousWorker();

        public AsyncWork()
        {
            _worker.BusyChanged += WorkerBusyChanged;
        }

        public AsyncWork(int maxConcurrentWorkItems)
        {
            _worker.SetMaxRunningWorkItems(maxConcurrentWorkItems);
            _worker.BusyChanged += WorkerBusyChanged;
        }

        void WorkerBusyChanged(object sender, BusyChangedEventArgs args)
        {
            var handler = BusyChanged;
            if (handler != null)
                handler(this, new BusyChangedEventArgs(IsBusy));
        }

        public bool IsBusy
        {
            get { return _worker.IsBusy; }
        }

        public event BusyChangedEventHandler BusyChanged; 

        /// <summary>
        /// Adds work to the AsyncWork.
        /// </summary>
        /// <param name="work">An action to perform asynchronously</param>
        /// <returns>Work Context to handle completion of this work</returns>
        public IWorkContext AddWork(Action work)
        {
            lock (_work)
            {
                var newWork = new WorkContext(this, work);
                _work.Enqueue(newWork);
                return newWork;
            }
        }

        public IWorkContext AddWork<TArg>(Func<TArg, AsyncCallback, object, IAsyncResult> work, TArg asyncArgument, Action<IAsyncResult> complete)
        {
            lock (_work)
            {
                var newWork = new WorkContext(this, AsyncSynchroniser.MakeSynchronous(work, asyncArgument, complete));

                _work.Enqueue(newWork);
                return newWork;
            }
        }

        /// <summary>
        /// Adds work to the AsyncWork.
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="work">The work which returns an object. Use .WhenComplete to access result</param>
        /// <returns>Work Context to handle completion of this work</returns>
        public IWorkContext<TResult> AddWork<TResult>(Func<TResult> work)
        {
            lock (_work)
            {
                var newWork = new WorkContext<TResult>(this, work);

                _work.Enqueue(newWork);
                return newWork;
            }
        }

        public IWorkContext AddWork(Func<AsyncCallback, object, IAsyncResult> work, Action<IAsyncResult> complete)
        {
            lock (_work)
            {
                var newWork = new WorkContext(this, AsyncSynchroniser.MakeSynchronous(work, complete));

                _work.Enqueue(newWork);
                return newWork;
            }
        }

        public IWorkContext<TResult> AddWork<TResult>(Func<AsyncCallback, object, IAsyncResult> work, Func<IAsyncResult, TResult> complete)
        {
            lock (_work)
            {
                var newWork = new WorkContext<TResult>(this, AsyncSynchroniser.MakeSynchronous(work, complete));

                _work.Enqueue(newWork);
                return newWork;
            }
        }

        public IWorkContext<TResult> AddWork<TArg, TResult>(Func<TArg, AsyncCallback, object, IAsyncResult> work, TArg asyncArgument, Func<IAsyncResult, TResult> complete)
        {
            lock (_work)
            {
                var newWork = new WorkContext<TResult>(this, AsyncSynchroniser.MakeSynchronous(work, asyncArgument, complete));

                _work.Enqueue(newWork);
                return newWork;
            }
        }

        public IWorkContext AddWork<TArg, TArg2>(Func<TArg, TArg2, AsyncCallback, object, IAsyncResult> work, TArg asyncArgument, TArg2 asyncArgument2, Action<IAsyncResult> complete)
        {
            lock (_work)
            {
                var newWork = new WorkContext(this, AsyncSynchroniser.MakeSynchronous(work, asyncArgument, asyncArgument2, complete));

                _work.Enqueue(newWork);
                return newWork;
            }
        }

        public IWorkContext<TResult> AddWork<TArg, TArg2, TResult>(Func<TArg, TArg2, AsyncCallback, object, IAsyncResult> work, TArg asyncArgument, TArg2 asyncArgument2, Func<IAsyncResult, TResult> complete)
        {
            lock (_work)
            {
                var newWork = new WorkContext<TResult>(this, AsyncSynchroniser.MakeSynchronous(work, asyncArgument, asyncArgument2, complete));

                _work.Enqueue(newWork);
                return newWork;
            }
        }

        /// <summary>
        /// Whens all work completes. Including chained work (work added during completed callbacks).
        /// </summary>
        /// <param name="completedCallback">The completed callback.</param>
        /// <returns></returns>
        public AsyncWork WhenAllComplete(Action completedCallback)
        {
            _completeCallback = completedCallback;
            return this;
        }

        /// <summary>
        /// Performs the work.
        /// </summary>
        public void PerformWork()
        {
            lock (_work)
            {
                while (_work.Count > 0)
                {
                    _worker.AddWork(_work.Dequeue().Build());
                }

                _worker.Start(_completeCallback);
            }
        }

        /// <summary>
        /// Waits for completion.
        /// </summary>
        public void WaitForCompletion()
        {
            if (!_worker.Started)
                throw new InvalidOperationException("You must call Start before calling WaitForCompletion()");

            _worker.WaitForCompletion();
        }

        /// <summary>
        /// Adds a work item to the unit of work. 
        /// </summary>
        /// <param name="workItem">The work item.</param>
        public void AddWork(WorkItem workItem)
        {
            lock (_work)
            {
                _worker.AddWork(workItem);
            }
        }
    }
}